//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//     Filename: AUSlotsXMLTranslator.cpp                                   //
//  Create date: Oct 03, 2005                                               //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//  Description: This file implements the AUSlotsXMLTranslator class.       //
//                This class is a protocol translator that translates from  //
//                the RTG "Australian" Slots (Dynamite Games) server        //
//                protocol to XML and back.                                 //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////
//       Copyright (c) 2005 - Realtime Gaming.  All rights reserved.        //
//////////////////////////////////////////////////////////////////////////////

#include <windows.h>
#include <stdio.h>
#include "lib.h"
#include "AUSlotsXMLTranslator.h"
#include "SlotsBaseCommon.h"
#include "XMLDoc.h"
#include "stdncerr.h"
#include "endian.h"
#include "log.h"

//////////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////////
AUSlotsXMLTranslator::AUSlotsXMLTranslator( Server* pServer, Connection* pParent ) :
											        SlotsXMLTranslator( pServer, pParent )
{
    // Override slot c-tor settings
    SetGameID( 18 );
    SetNumBytesMsgLen( 2 );
}

//////////////////////////////////////////////////////////////////////////////
// Destructor
//////////////////////////////////////////////////////////////////////////////
AUSlotsXMLTranslator::~AUSlotsXMLTranslator()
{
}

//////////////////////////////////////////////////////////////////////////////
// TranslateIncomingMessage
//////////////////////////////////////////////////////////////////////////////
bool8 AUSlotsXMLTranslator::TranslateIncomingMessage( uint8* pGameInBuff,
												      uint32 nGameInBuffSize,
												      uint8* pOutBuff,
												      uint32 nOutBuffSize )
{
    bool8 bRetVal = false;

	// Handle the messages.  Note, I'm only going to look at the first character
	//  in a small attempt to optimize a bit
 	switch( m_sMsgID[0] )
	{
		case 'L':
			bRetVal = TranslateInLogin( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		case 'S':
			bRetVal = TranslateInSpin( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		case 'N':
			bRetVal = TranslateInNextSpin( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		case 'B':
			if ( m_sMsgID[1] == 'o' )
			{
				bRetVal = TranslateInBonus( m_pDoc, pGameInBuff, nGameInBuffSize );
			}
			else if ( m_sMsgID[1] == 'a' )
			{
				bRetVal = TranslateInBalance( m_pDoc, pGameInBuff, nGameInBuffSize, GetNumBytesMsgLen() );
			}
			else
			{
				ASSERT(FALSE);
			}
			break;

		case 'C':
			bRetVal = TranslateInCustom( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		case 'J':
			bRetVal = TranslateInJackpot( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		case 'I':
			bRetVal = TranslateInIcons( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		default:
			String sMsg = TEXT("Error, received unknown client message: ");
			sMsg += m_sMsgID;
			Log::msgError( sMsg );
			break;
	}

	// Report Error
	if( !bRetVal )
	{
		if ( m_sMsgID[0] == 'L' )
		{
			// Set an error into the out buffer
			ReturnLoginError( pOutBuff, nOutBuffSize );
		}
		else
		{
			// Set an error into the out buffer
			ReturnErrorMessage( TEXT("ServerError"), pOutBuff, nOutBuffSize );

			String sMsg = TEXT("Error translating incoming message: ");
			sMsg += m_sMsgID;
			Log::msgError( sMsg );
		}
	}

	return bRetVal;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutgoingMessage
//////////////////////////////////////////////////////////////////////////////
uint32 AUSlotsXMLTranslator::TranslateOutgoingMessage( uint8* pOutBuff,
												       uint32 nOutBuffSize,
												       uint8* pGameOutBuff,
												       uint32 nGameOutBuffSize,
												       uint32 nTotalGameBytes    )
{
	bool8 bResult = true;
	uint32 nGameBytesUsed = 0;
	uint32 nOutBytesUsed = 0;

	while ( bResult && (nGameBytesUsed < nTotalGameBytes) )
	{
		// Keep track of buffers as we might be processing more than one game
		//   message at a time.
		uint8* pCurGameBuf     = pGameOutBuff     + nGameBytesUsed;
		uint32 nCurGameBufSize = nGameOutBuffSize - nGameBytesUsed;
		uint8* pCurOutBuf      = pOutBuff         + nOutBytesUsed;
		uint32 nCurOutBufSize  = nOutBuffSize     - nOutBytesUsed;

		// Make sure buffer has not been overrun!
		if ( nOutBytesUsed >= nOutBuffSize )    // kharmon.  01-23-06.  #8895.  Write error even if buffer is just
		{                                       // full, because there's not going to be enough room for anything else.			
			ASSERT(false);
			Log::msgError( "Error, out buffer overflow!" );
			return 0;
		}

		// Now the actual message handlers!
		switch( pCurGameBuf[2] )
		{
			case 'L':
				bResult = TranslateOutLogin( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;
			
			case 'm':
				bResult = TranslateOutBalance( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize, 2 );
				break;

			case 'R':
				bResult = TranslateOutReels( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'P':
				bResult = TranslateOutPayouts( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'Y':
				bResult = TranslateOutPaylines( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'D':
				bResult = TranslateOutDenominations( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'J':
				bResult = TranslateOutJackpot( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'I':
				bResult = TranslateOutIcons( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'G':
				bResult = TranslateOutSubGame( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'S':
				bResult = TranslateOutSpin( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'V':
				bResult = TranslateOutSave( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'E':
				bResult = TranslateGenericOutError( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize, 2 );
				break;

			case 'B':
				bResult = TranslateOutBonus( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'N':
			case '$':
				bResult = TranslateOutError( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			default:
				bResult = ReturnErrorMessage( TEXT("UnknownMessage"), pCurOutBuf, nCurOutBufSize );
				Log::msgError( "Received unknown game message: %c", pCurGameBuf[2] );
				break;
		}

		// Check and see if any errors occurred
		if ( !bResult )
		{
			ReturnErrorMessage( TEXT("ParseError"), pCurOutBuf, nCurOutBufSize );
			Log::msgError( "Error translating outgoing message: %c", pCurGameBuf[1] );
		}

		// We know that the size of the message is always stored in the first
		//  2 bytes of the client message.
		uint16 nMsgLen;
		Endian::ReceiveInt2( &nMsgLen, pCurGameBuf );
		nGameBytesUsed += nMsgLen;

		// Out XML Messages are supposed to be NULL Terminated
		nOutBytesUsed += strlen( (char*) pCurOutBuf );
		nOutBytesUsed++;  // Add one for the NULL

		DEBUGMSG( ("\n***Outgoing: %s", pCurOutBuf) );
	}

	return nOutBytesUsed;
}

//****************************************************************************
// Incoming
//****************************************************************************

//////////////////////////////////////////////////////////////////////////////
// TranslateInSpin
//////////////////////////////////////////////////////////////////////////////
bool8 AUSlotsXMLTranslator::TranslateInSpin( XMLDoc* pDoc, uint8* pGameInBuff,  uint32 nGameInBuffSize )
{
    // Message length for slots is 2 BYTES!!!
	uint32 nOffset = 2;

	// Write Message ID
	pGameInBuff[nOffset++] = 'S';

	// Write num lines
	uint8 nNumLines;
	if ( !pDoc->GetSingleNodeByte( TEXT("//num_lines"), nNumLines ) )
	{
		return false;
	}	
    pGameInBuff[nOffset++] = nNumLines;

	// Bet per line
	uint32 nBet;
	if ( !pDoc->GetSingleNodeLong( TEXT("//bet_per_line"), nBet ) )
	{
		return false;
	}
	Endian::SendInt4( nBet, pGameInBuff + nOffset );
    nOffset += 4;

    // Autoplay
    bool8 bAutoplay = false;
    if ( !pDoc->GetSingleNodeBool( TEXT("//autoplay"), bAutoplay ) )
    {
        return false;
    }
    pGameInBuff[nOffset++] = bAutoplay;

    ////////////////////////////////////////////////////////////////
	// Finally, add message size
	////////////////////////////////////////////////////////////////
	// Message length for slots is 2 BYTES!!!
	Endian::SendInt2( (uint16) nOffset, pGameInBuff );

	return true;
}

//****************************************************************************
// Outgoing
//****************************************************************************

//////////////////////////////////////////////////////////////////////////////
// TranslateOutLogin
//////////////////////////////////////////////////////////////////////////////
bool8 AUSlotsXMLTranslator::TranslateOutLogin( uint8* pOutBuff,
										       uint32 nOutBuffSize,
										       uint8* pGameOutBuff,
										       uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Login") ) )
	{
		return false;
	}

// Check message length
#ifdef DEBUG
	uint16 nMsgLen;
	Endian::ReceiveInt2( &nMsgLen, pGameOutBuff );
	ASSERT( nMsgLen == 8 );
#endif

	// Balance
	int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff+3 );
	if ( !doc.AddRootChildNode( TEXT("balance"), (uint32) nValue ) )
	{
		return false;
	}

	// Progressive
	bool8 bFlag = pGameOutBuff[7];  // kharmon.  04-05-07.  #19308.  We were reading [8]...one too far.
	if ( !doc.AddRootChildNode( TEXT("progressive"), bFlag ) )
	{
		return false;
	}

	if ( !AddCurrencyOutput(&doc) )	// CSN-11204: return currency
	{
		return false;
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutReels
//////////////////////////////////////////////////////////////////////////////
bool8 AUSlotsXMLTranslator::TranslateOutReels( uint8* pOutBuff,
										       uint32 nOutBuffSize,
										       uint8* pGameOutBuff,
										       uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Reels") ) )
	{
		return false;
	}

	// Starting offset for mesage translation
	uint32 nOffset = 3;

    // Num Wheelhouses
	uint8 nNumWheelhouses = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_wheelhouses"), (uint32) nNumWheelhouses ) )
	{
		return false;
	}

    // Loop through all the wheelhouses
    for( uint8 n = 0; n < nNumWheelhouses; n++ )
    {
        // Add the node for the wheelhouse
        String sWheelHouseNode;
        sWheelHouseNode.sprintf( TEXT("wheelhouse_%d"), n );

        if ( !doc.AddRootChildNode( sWheelHouseNode ) )
        {
            return false;
        }

        // Num Reels
        uint8 nNumReels = pGameOutBuff[nOffset++];

        String sWheelHouseNodeQuery = TEXT("//");
        sWheelHouseNodeQuery += sWheelHouseNode;

        // Write out num icons
        if ( !doc.AddChildNode( sWheelHouseNodeQuery, TEXT("num_reels"), (uint32) nNumReels ) )
        {
            return false;
        }

        // Loop through all reels
		for( uint8 i = 0; i < nNumReels; i++ )
        {
            // Add the child node for the reel
            String sReelNode;
            sReelNode.sprintf( TEXT("reel_%d"), i );

            if ( !doc.AddChildNode( sWheelHouseNodeQuery, sReelNode ) )
            {
                return false;
            }

            // Get the number of icons for this reel
            uint8 nNumIcons = pGameOutBuff[nOffset++];

            String sQuery = sWheelHouseNodeQuery;
            sQuery += TEXT("/");
            sQuery += sReelNode;

            // Write out num icons
            if ( !doc.AddChildNode( sQuery, TEXT("num_icons"), (uint32) nNumIcons ) )
            {
                return false;
            }

            // Loop through all the icons
            for( uint8 j = 0; j < nNumIcons; j++ )
            {
                // Get the icon value
                uint8 nIcon = pGameOutBuff[nOffset++];

                // Add the child node for the icon
                String sIconNode;
                sIconNode.sprintf( TEXT("icon_%d"), j );

                if ( !doc.AddChildNode( sQuery, sIconNode, (uint32) nIcon ) )
                {
                    return false;
                }
            }
        }
    }

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

    //FILE* f = fopen( "c:\\reels.xml", "wt" );
    //fwrite( pOutBuff, sizeof( char ), strlen( (const char *)pOutBuff), f );
    //fclose( f );

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutSpin
//////////////////////////////////////////////////////////////////////////////
bool8 AUSlotsXMLTranslator::TranslateOutSpin( uint8* pOutBuff,
										      uint32 nOutBuffSize,
									          uint8* pGameOutBuff,
								    	      uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Spin") ) )
	{
		return false;
	}

	// Starting offset for mesage translation
	uint32 nOffset = 3;

	// Balance
	int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
	if ( !doc.AddRootChildNode( TEXT("balance"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Num Reels
	uint8 nNumReels = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_reels"), (uint32) nNumReels ) )
	{
		return false;
	}

	// Game mode
	uint8 nGameMode = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("game_mode"), (uint32) nGameMode ) )
	{
		return false;
	}

    // Wheel stops - 1 per reel
    for( uint8 i = 0; i < nNumReels; i++ )
    {
        // Get the wheel stop for each
        uint8 nWheelStop = pGameOutBuff[nOffset++];

        // Add the node for the wheel stop
        String sWSNode;
        sWSNode.sprintf( TEXT("wheel_stop_%d"), i );

        if ( !doc.AddRootChildNode( sWSNode, (uint32) nWheelStop ) )
        {
            return false;
        }
    }

    // Num Lines
	uint8 nNumLines = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_lines"), (uint32) nNumLines ) )
	{
		return false;
	}

    // Loop through all the Lines
    for( i = 0; i < nNumLines; i++ )
    {
        // Add the child node for the line
        String sLine;
        sLine.sprintf( TEXT("line_%d"), i );

        if ( !doc.AddRootChildNode( sLine ) )
        {
            return false;
        }

		String sLineQuery = TEXT("//");
		sLineQuery += sLine;

		// Line Number
        uint8 nLinNum = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sLineQuery, TEXT("line_num"), (uint32) nLinNum ) )
		{
			return false;
		}

		// Win Type
		uint8 nWinType = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sLineQuery, TEXT("win_type"), (uint32) nWinType ) )
		{
			return false;
		}

        // Base Win Amount
		int32 nValue;
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sLineQuery, TEXT("base_win_amt"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

        // Line total multiplier
		nValue;
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sLineQuery, TEXT("multiplier"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

        // Total Win Amount
		nValue;
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sLineQuery, TEXT("total_win_amt"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

		// Jackpot?
		bool8 bJackpot = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sLineQuery, TEXT("jackpot"), bJackpot ) )
		{
			return false;
		}

		// Number of notes
		uint8 nNumNotes = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sLineQuery, TEXT("num_notes"), (uint32) nNumNotes ) )
		{
			return false;
		}

        // Loop through the notes
        for( uint8 j = 0; j < nNumNotes; j++ )
        {		
            String sNote;
            sNote.sprintf( TEXT("note_%d"), j );

            uint8 nNote = pGameOutBuff[nOffset++];
		    if ( !doc.AddChildNode( sLineQuery, sNote, (uint32) nNote ) )
		    {
			    return false;
		    }
        }
    }

    // Num Scatters
	uint8 nNumScatters = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_scatters"), (uint32) nNumScatters ) )
	{
		return false;
	}

    // Loop through all the Scatters
    for( i = 0; i < nNumScatters; i++ )
    {
        // Add the child node for the line
        String sScatter;
        sScatter.sprintf( TEXT("scatter_%d"), i );

        if ( !doc.AddRootChildNode( sScatter ) )
        {
            return false;
        }

		String sScatQuery = TEXT("//");
		sScatQuery += sScatter;

		// Scatter Number
        uint8 nScatNum = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sScatQuery, TEXT("scatter_num"), (uint32) nScatNum ) )
		{
			return false;
		}

        // Position
		int32 nValue;
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sScatQuery, TEXT("scatter_pos"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

		// Win Type
		uint8 nWinType = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sScatQuery, TEXT("win_type"), (uint32) nWinType ) )
		{
			return false;
		}

        // Base Win Amount
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sScatQuery, TEXT("base_win_amt"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

        // Line total multiplier
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sScatQuery, TEXT("multiplier"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

        // Total Win Amount
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sScatQuery, TEXT("total_win_amt"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

		// Jackpot?
		bool8 bJackpot = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sScatQuery, TEXT("jackpot"), bJackpot ) )
		{
			return false;
		}

        // Free Spins
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sScatQuery, TEXT("free_spins"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

		// Number of notes
		uint8 nNumNotes = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sScatQuery, TEXT("num_notes"), (uint32) nNumNotes ) )
		{
			return false;
		}

        // Loop through the notes
        for( uint8 j = 0; j < nNumNotes; j++ )
        {		
            String sNote;
            sNote.sprintf( TEXT("note_%d"), j );

            uint8 nNote = pGameOutBuff[nOffset++];
		    if ( !doc.AddChildNode( sScatQuery, sNote, (uint32) nNote ) )
		    {
			    return false;
		    }
        }
    }

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

    // FILE* f = fopen( "c:\\spins.xml", "wt" );
    // fwrite( pOutBuff, sizeof( char ), strlen( (const char *)pOutBuff), f );
    // fclose( f );

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutSave
//////////////////////////////////////////////////////////////////////////////
bool8 AUSlotsXMLTranslator::TranslateOutSave( uint8* pOutBuff,
										      uint32 nOutBuffSize,
									          uint8* pGameOutBuff,
								    	      uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Spin") ) )
	{
		return false;
	}

	// Starting offset for mesage translation
	uint32 nOffset = 3;

	// Bet Per Line
	int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
	if ( !doc.AddRootChildNode( TEXT("bet_per_line"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Num Reels
	uint8 nNumReels = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_reels"), (uint32) nNumReels ) )
	{
		return false;
	}

	// Game mode
	uint8 nGameMode = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("game_mode"), (uint32) nGameMode ) )
	{
		return false;
	}

    // Wheel stops - 1 per reel
    for( uint8 i = 0; i < nNumReels; i++ )
    {
        // Get the wheel stop for each
        uint8 nWheelStop = pGameOutBuff[nOffset++];

        // Add the node for the wheel stop
        String sWSNode;
        sWSNode.sprintf( TEXT("wheel_stop_%d"), i );

        if ( !doc.AddRootChildNode( sWSNode, (uint32) nWheelStop ) )
        {
            return false;
        }
    }

    // Num Lines
	uint8 nNumLines = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_lines"), (uint32) nNumLines ) )
	{
		return false;
	}

    // Loop through all the Lines
    for( i = 0; i < nNumLines; i++ )
    {
        // Add the child node for the line
        String sLine;
        sLine.sprintf( TEXT("line_%d"), i );

        if ( !doc.AddRootChildNode( sLine ) )
        {
            return false;
        }

		String sLineQuery = TEXT("//");
		sLineQuery += sLine;

		// Line Number
        uint8 nLinNum = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sLineQuery, TEXT("line_num"), (uint32) nLinNum ) )
		{
			return false;
		}

		// Win Type
		uint8 nWinType = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sLineQuery, TEXT("win_type"), (uint32) nWinType ) )
		{
			return false;
		}

        // Base Win Amount
		int32 nValue;
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sLineQuery, TEXT("base_win_amt"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

        // Line total multiplier
		nValue;
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sLineQuery, TEXT("multiplier"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

        // Total Win Amount
		nValue;
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sLineQuery, TEXT("total_win_amt"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

		// Jackpot?
		bool8 bJackpot = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sLineQuery, TEXT("jackpot"), bJackpot ) )
		{
			return false;
		}

		// Number of notes
		uint8 nNumNotes = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sLineQuery, TEXT("num_notes"), (uint32) nNumNotes ) )
		{
			return false;
		}

        // Loop through the notes
        for( uint8 j = 0; j < nNumNotes; j++ )
        {		
            String sNote;
            sNote.sprintf( TEXT("note_%d"), j );

            uint8 nNote = pGameOutBuff[nOffset++];
		    if ( !doc.AddChildNode( sLineQuery, sNote, (uint32) nNote ) )
		    {
			    return false;
		    }
        }
    }

    // Num Scatters
	uint8 nNumScatters = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_scatters"), (uint32) nNumScatters ) )
	{
		return false;
	}

    // Loop through all the Scatters
    for( i = 0; i < nNumScatters; i++ )
    {
        // Add the child node for the line
        String sScatter;
        sScatter.sprintf( TEXT("scatter_%d"), i );

        if ( !doc.AddRootChildNode( sScatter ) )
        {
            return false;
        }

		String sScatQuery = TEXT("//");
		sScatQuery += sScatter;

		// Scatter Number
        uint8 nScatNum = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sScatQuery, TEXT("scatter_num"), (uint32) nScatNum ) )
		{
			return false;
		}

        // Positions
		int32 nValue;
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sScatQuery, TEXT("scatter_pos"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

		// Win Type
		uint8 nWinType = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sScatQuery, TEXT("win_type"), (uint32) nWinType ) )
		{
			return false;
		}

        // Base Win Amount
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sScatQuery, TEXT("base_win_amt"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

        // Line total multiplier
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sScatQuery, TEXT("multiplier"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

        // Total Win Amount
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sScatQuery, TEXT("total_win_amt"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

		// Jackpot?
		bool8 bJackpot = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sScatQuery, TEXT("jackpot"), bJackpot ) )
		{
			return false;
		}

        // Free Spins
		Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		if ( !doc.AddChildNode( sScatQuery, TEXT("free_spins"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

		// Number of notes
		uint8 nNumNotes = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sScatQuery, TEXT("num_notes"), (uint32) nNumNotes ) )
		{
			return false;
		}

        // Loop through the notes
        for( uint8 j = 0; j < nNumNotes; j++ )
        {		
            String sNote;
            sNote.sprintf( TEXT("note_%d"), j );

            uint8 nNote = pGameOutBuff[nOffset++];
		    if ( !doc.AddChildNode( sScatQuery, sNote, (uint32) nNote ) )
		    {
			    return false;
		    }
        }
    }

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

    //FILE* f = fopen( "c:\\saved.xml", "wt" );
    //fwrite( pOutBuff, sizeof( char ), strlen( (const char *)pOutBuff), f );
    //fclose( f );

	return true;
}

// FogBugz 20047
//
// Flash clients are not correctly handling the new base-class subgame data 
// introduced for Field of Green, so for now lets skip these entries when
// using the XML protocol.
static bool skipSubgameItem( uint32 nItemID )
{
    return nItemID >= SGIDX_FEATURE_TRIGGERED && nItemID < SGDIDX_END_BASE_SUB_GAME_DATA_RESERVED_RANGE;
}

static uint8 getSkippedSubgameItemCount( uint8* pGameOutBuff, uint32 nOffset, uint8 nNumItems )
{
    uint32 nSkipped = 0;

	// Loop through all the items
	for( uint8 i = 0; i < nNumItems; i++ )
	{
		// Get the ID of each item
        int32 nItemID;
    	Endian::ReceiveInt4( &nItemID, pGameOutBuff + nOffset );
        nOffset += 4;

        // Skip past the value
        nOffset += 4;

        if ( skipSubgameItem(nItemID) )
        {
            ++nSkipped;
        }
    }
    
    return nSkipped;
}


//////////////////////////////////////////////////////////////////////////////
// TranslateOutSubGame
//////////////////////////////////////////////////////////////////////////////
bool8 AUSlotsXMLTranslator::TranslateOutSubGame( uint8* pOutBuff,
										         uint32 nOutBuffSize,
									             uint8* pGameOutBuff,
								    	         uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("SubGame") ) )
	{
		return false;
	}

	// Starting offset for mesage translation
	uint32 nOffset = 3;

	// Num of items
	uint8 nNumItems = pGameOutBuff[nOffset++];
    uint8 nSkipped = getSkippedSubgameItemCount( pGameOutBuff, nOffset, nNumItems );
	if ( !doc.AddRootChildNode( TEXT("num_items"), (uint32) nNumItems - nSkipped ) )
	{
		return false;
	}

    int index = 0;

	// Loop through all the items
	for( uint8 i = 0; i < nNumItems; i++ )
	{
		// Get the ID of each item
        int32 nItemID;
    	Endian::ReceiveInt4( &nItemID, pGameOutBuff + nOffset );
        nOffset += 4;

		// Add the child node for the payline
		String sIDNode;
		sIDNode.sprintf( TEXT("id_%d"), index );

		// Get the actual data for each item
        int32 nData;
    	Endian::ReceiveInt4( &nData, pGameOutBuff + nOffset );
        nOffset += 4;

		// Add the child node for the payline
		String sDataNode;
		sDataNode.sprintf( TEXT("data_%d"), index );

        if ( ! skipSubgameItem(nItemID) )
        {
		    if ( !doc.AddRootChildNode( sIDNode, (uint32) nItemID ) )
		    {
			    return false;
		    }

		    if ( !doc.AddRootChildNode( sDataNode, (uint32) nData ) )
		    {
			    return false;
		    }

            ++index;
        }
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

    //FILE* f = fopen( "c:\\subgame.xml", "wt" );
    //fwrite( pOutBuff, sizeof( char ), strlen( (const char *)pOutBuff), f );
    //fclose( f );

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutBonus
//////////////////////////////////////////////////////////////////////////////
bool8 AUSlotsXMLTranslator::TranslateOutBonus( uint8* pOutBuff,
										       uint32 nOutBuffSize,
									           uint8* pGameOutBuff,
  								    	       uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Bonus") ) )
	{
		return false;
	}

	// Starting offset for message translation
	uint32 nOffset = 3;

	// Number of Games
	int32 nNumGames;
	Endian::ReceiveInt4( &nNumGames, pGameOutBuff + nOffset );
	if ( !doc.AddRootChildNode( TEXT("num_games"), (uint32) nNumGames ) )
	{
		return false;
	}
	nOffset += 4;

	// Loop	through all the games
	for ( int32 i = 0; i < nNumGames; i++)
	{
        // Add the child node for the game
        String sGame;
        sGame.sprintf( TEXT("game_%d"), i );

        if ( !doc.AddRootChildNode( sGame ) )
        {
            return false;
        }

		String sGameQuery = TEXT("//");
		sGameQuery += sGame;

		// Bonus Game Type
        uint8 nBonusGameType = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sGameQuery, TEXT("bonus_game_type"), (uint32) nBonusGameType ) )
		{
			return false;
		}

		// Bonus Game Index
		uint8 nBonusGameIndex = pGameOutBuff[nOffset++];
		if ( !doc.AddChildNode( sGameQuery, TEXT("bonus_game_index"), (uint32) nBonusGameIndex ) )
		{
			return false;
		}

        // Base Payout Amount
		int32 nValue;
		Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
		if ( !doc.AddChildNode( sGameQuery, TEXT("base_payout_amount"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

        // Total Multiplier
		nValue;
		Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
		if ( !doc.AddChildNode( sGameQuery, TEXT("total_multiplier"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

        // Total Bonus Payout Amount
		nValue;
		Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
		if ( !doc.AddChildNode( sGameQuery, TEXT("total_bonus_payout_amount"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;

        // Free Spins
		nValue;
		Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
		if ( !doc.AddChildNode( sGameQuery, TEXT("free_spins"), (uint32) nValue ) )
		{
			return false;
		}
		nOffset += 4;
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

//	FILE* f = fopen( "c:\\bonus.xml", "wt" );
//	fwrite( pOutBuff, sizeof( char ), strlen( (const char *)pOutBuff), f );
//	fclose( f );

	return true;
}
